/*
 * Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <arch_helpers.h>
#include <assert.h>
#include <cci.h>
#include <console.h>
#include <debug.h>
#include <gicv2.h>
#include <hi3660.h>
#include <hi3660_crg.h>
#include <mmio.h>
#include <psci.h>
#include "drivers/pwrc/hisi_pwrc.h"

#include "hikey960_def.h"
#include "hikey960_private.h"

#define CORE_PWR_STATE(state) \
    ((state)->pwr_domain_state[MPIDR_AFFLVL0])
#define CLUSTER_PWR_STATE(state) \
    ((state)->pwr_domain_state[MPIDR_AFFLVL1])
#define SYSTEM_PWR_STATE(state) \
    ((state)->pwr_domain_state[PLAT_MAX_PWR_LVL])

#define DMAC_GLB_REG_SEC    0x694
#define AXI_CONF_BASE        0x820

static uintptr_t hikey960_sec_entrypoint;

static void hikey960_pwr_domain_standby(plat_local_state_t cpu_state)
{
    unsigned long scr;
    unsigned int val = 0;

    assert(cpu_state == PLAT_MAX_RET_STATE);

    scr = read_scr_el3();

    /* Enable Physical IRQ and FIQ to wake the CPU*/
    write_scr_el3(scr | SCR_IRQ_BIT | SCR_FIQ_BIT);

    set_retention_ticks(val);
    wfi();
    clr_retention_ticks(val);

    /*
     * Restore SCR to the original value, synchronisazion of
     * scr_el3 is done by eret while el3_exit to save some
     * execution cycles.
     */
    write_scr_el3(scr);
}

static int hikey960_pwr_domain_on(u_register_t mpidr)
{
    unsigned int core = mpidr & MPIDR_CPU_MASK;
    unsigned int cluster =
        (mpidr & MPIDR_CLUSTER_MASK) >> MPIDR_AFFINITY_BITS;
    int cluster_stat = cluster_is_powered_on(cluster);

    hisi_set_cpu_boot_flag(cluster, core);

    mmio_write_32(CRG_REG_BASE + CRG_RVBAR(cluster, core),
              hikey960_sec_entrypoint >> 2);

    if (cluster_stat)
        hisi_powerup_core(cluster, core);
    else
        hisi_powerup_cluster(cluster, core);

    return PSCI_E_SUCCESS;
}

static void
hikey960_pwr_domain_on_finish(const psci_power_state_t *target_state)
{
    if (CLUSTER_PWR_STATE(target_state) == PLAT_MAX_OFF_STATE)
        cci_enable_snoop_dvm_reqs(MPIDR_AFFLVL1_VAL(read_mpidr_el1()));

    gicv2_pcpu_distif_init();
    gicv2_cpuif_enable();
}

void hikey960_pwr_domain_off(const psci_power_state_t *target_state)
{
    unsigned long mpidr = read_mpidr_el1();
    unsigned int core = mpidr & MPIDR_CPU_MASK;
    unsigned int cluster =
        (mpidr & MPIDR_CLUSTER_MASK) >> MPIDR_AFFINITY_BITS;

    clr_ex();
    isb();
    dsbsy();

    gicv2_cpuif_disable();

    hisi_clear_cpu_boot_flag(cluster, core);
    hisi_powerdn_core(cluster, core);

    /* check if any core is powered up */
    if (hisi_test_cpu_down(cluster, core)) {

        cci_disable_snoop_dvm_reqs(MPIDR_AFFLVL1_VAL(read_mpidr_el1()));

        isb();
        dsbsy();

        hisi_powerdn_cluster(cluster, core);
    }
}

static void __dead2 hikey960_system_reset(void)
{
    mmio_write_32(SCTRL_SCPEREN1_REG,
              SCPEREN1_WAIT_DDR_SELFREFRESH_DONE_BYPASS);
    mmio_write_32(SCTRL_SCSYSSTAT_REG, 0xdeadbeef);
    panic();
}

int hikey960_validate_power_state(unsigned int power_state,
                   psci_power_state_t *req_state)
{
    int pstate = psci_get_pstate_type(power_state);
    int pwr_lvl = psci_get_pstate_pwrlvl(power_state);
    int i;

    assert(req_state);

    if (pwr_lvl > PLAT_MAX_PWR_LVL)
        return PSCI_E_INVALID_PARAMS;

    /* Sanity check the requested state */
    if (pstate == PSTATE_TYPE_STANDBY) {
        /*
         * It's possible to enter standby only on power level 0
         * Ignore any other power level.
         */
        if (pwr_lvl != MPIDR_AFFLVL0)
            return PSCI_E_INVALID_PARAMS;

        req_state->pwr_domain_state[MPIDR_AFFLVL0] =
                    PLAT_MAX_RET_STATE;
    } else {
        for (i = MPIDR_AFFLVL0; i <= pwr_lvl; i++)
            req_state->pwr_domain_state[i] =
                    PLAT_MAX_OFF_STATE;
    }

    /*
     * We expect the 'state id' to be zero.
     */
    if (psci_get_pstate_id(power_state))
        return PSCI_E_INVALID_PARAMS;

    return PSCI_E_SUCCESS;
}

static int hikey960_validate_ns_entrypoint(uintptr_t entrypoint)
{
    /*
     * Check if the non secure entrypoint lies within the non
     * secure DRAM.
     */
    if ((entrypoint > DDR_BASE) && (entrypoint < (DDR_BASE + DDR_SIZE)))
        return PSCI_E_SUCCESS;

    return PSCI_E_INVALID_ADDRESS;
}

static void hikey960_pwr_domain_suspend(const psci_power_state_t *target_state)
{
    u_register_t mpidr = read_mpidr_el1();
    unsigned int core = mpidr & MPIDR_CPU_MASK;
    unsigned int cluster =
        (mpidr & MPIDR_CLUSTER_MASK) >> MPIDR_AFFINITY_BITS;

    if (CORE_PWR_STATE(target_state) != PLAT_MAX_OFF_STATE)
        return;

    if (CORE_PWR_STATE(target_state) == PLAT_MAX_OFF_STATE) {
        clr_ex();
        isb();
        dsbsy();

        gicv2_cpuif_disable();

        hisi_cpuidle_lock(cluster, core);
        hisi_set_cpuidle_flag(cluster, core);
        hisi_cpuidle_unlock(cluster, core);

        mmio_write_32(CRG_REG_BASE + CRG_RVBAR(cluster, core),
              hikey960_sec_entrypoint >> 2);

        hisi_enter_core_idle(cluster, core);
    }

    /* Perform the common cluster specific operations */
    if (CLUSTER_PWR_STATE(target_state) == PLAT_MAX_OFF_STATE) {
        hisi_cpuidle_lock(cluster, core);
        hisi_disable_pdc(cluster);

        /* check if any core is powered up */
        if (hisi_test_pwrdn_allcores(cluster, core)) {

            cci_disable_snoop_dvm_reqs(MPIDR_AFFLVL1_VAL(mpidr));

            isb();
            dsbsy();

            /* mask the pdc wakeup irq, then
             * enable pdc to power down the core
             */
            hisi_pdc_mask_cluster_wakeirq(cluster);
            hisi_enable_pdc(cluster);

            hisi_cpuidle_unlock(cluster, core);

            /* check the SR flag bit to determine
             * CLUSTER_IDLE_IPC or AP_SR_IPC to send
             */
            if (hisi_test_ap_suspend_flag(cluster))
                hisi_enter_ap_suspend(cluster, core);
            else
                hisi_enter_cluster_idle(cluster, core);
        } else {
            /* enable pdc */
            hisi_enable_pdc(cluster);
            hisi_cpuidle_unlock(cluster, core);
        }
    }
}

static void hikey960_sr_dma_reinit(void)
{
    unsigned int ctr = 0;

    mmio_write_32(DMAC_BASE + DMAC_GLB_REG_SEC, 0x3);

    /* 1~15 channel is set non_secure */
    for (ctr = 1; ctr <= 15; ctr++)
        mmio_write_32(DMAC_BASE + AXI_CONF_BASE + ctr * (0x40),
                  (1 << 6) | (1 << 18));
}

static void
hikey960_pwr_domain_suspend_finish(const psci_power_state_t *target_state)
{
    unsigned long mpidr = read_mpidr_el1();
    unsigned int cluster =
        (mpidr & MPIDR_CLUSTER_MASK) >> MPIDR_AFFINITY_BITS;

    /* Nothing to be done on waking up from retention from CPU level */
    if (CORE_PWR_STATE(target_state) != PLAT_MAX_OFF_STATE)
        return;

    if (hisi_test_ap_suspend_flag(cluster)) {
        hikey960_sr_dma_reinit();
        gicv2_cpuif_enable();
        console_init(PL011_UART6_BASE, PL011_UART_CLK_IN_HZ,
                 PL011_BAUDRATE);
    }

    hikey960_pwr_domain_on_finish(target_state);
}

static void hikey960_get_sys_suspend_power_state(psci_power_state_t *req_state)
{
    int i;

    for (i = MPIDR_AFFLVL0; i <= PLAT_MAX_PWR_LVL; i++)
        req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
}

static const plat_psci_ops_t hikey960_psci_ops = {
    .cpu_standby            = hikey960_pwr_domain_standby,
    .pwr_domain_on            = hikey960_pwr_domain_on,
    .pwr_domain_on_finish        = hikey960_pwr_domain_on_finish,
    .pwr_domain_off            = hikey960_pwr_domain_off,
    .pwr_domain_suspend        = hikey960_pwr_domain_suspend,
    .pwr_domain_suspend_finish    = hikey960_pwr_domain_suspend_finish,
    .system_off            = NULL,
    .system_reset            = hikey960_system_reset,
    .validate_power_state        = hikey960_validate_power_state,
    .validate_ns_entrypoint        = hikey960_validate_ns_entrypoint,
    .get_sys_suspend_power_state    = hikey960_get_sys_suspend_power_state,
};

int plat_setup_psci_ops(uintptr_t sec_entrypoint,
            const plat_psci_ops_t **psci_ops)
{
    hikey960_sec_entrypoint = sec_entrypoint;

    INFO("%s: sec_entrypoint=0x%lx\n", __func__,
         (unsigned long)hikey960_sec_entrypoint);

    /*
     * Initialize PSCI ops struct
     */
    *psci_ops = &hikey960_psci_ops;
    return 0;
}
